package com.shermanzhou.springbootflowable.demo;

import com.alibaba.fastjson.JSONObject;
import com.shermanzhou.springbootflowable.demo.flow.ApprovalFlow;
import com.shermanzhou.springbootflowable.demo.flow.ApprovalFlowNode;
import com.shermanzhou.springbootflowable.demo.flow2.EngineApprovalFlowNode2;
import com.shermanzhou.springbootflowable.demo.flow2.EngineApprovalFlowResult;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhouchaoqun02
 * @date 2020-06-14 19:55
 */
@SpringBootTest
public class ApprovalFlowTest4 {

    private static final String SUB_PROCESS_TYPE_NAME = "subProcess";

    @Autowired
    @Qualifier("normalProcessEngine")
    private ProcessEngine normalEngine;

    @Autowired
    @Qualifier("simulateProcessEngine")
    private ProcessEngine simulateEngine;

    @Test
    public void finishedProcInstFlow() {
        String procInstId = "7501";
        HistoryService historyService = normalEngine.getHistoryService();
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();

        List<HistoricTaskInstance> hisTasks = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().includeTaskLocalVariables().processInstanceId(procInstId).list();

        List<HistoricActivityInstance> actHistoryList = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId).list();
        Process process = normalEngine.getRepositoryService().getBpmnModel(historicProcessInstance.getProcessDefinitionId()).getProcesses().get(0);
        List<HistoricVariableInstance> historicVariables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procInstId).list();

        long t1 = System.currentTimeMillis();
        EngineApprovalFlowResult flowResult = makeUpApprovalFlow(process, historicVariables, actHistoryList, hisTasks);
        long t2 = System.currentTimeMillis();

        System.out.println(JSONObject.toJSONString(flowResult));


        System.out.println("execute cost: " + (t2 - t1));
    }

    private String nodeId(String actId, String flowId) {
        return String.format("%s@%s", actId, flowId);
    }

    private String flowId(String executionId) {
        return String.format("%s_%s", "flow", executionId);
    }

    private EngineApprovalFlowResult makeUpApprovalFlow(Process process,
                                                        List<HistoricVariableInstance> variableInstances,
                                                        List<HistoricActivityInstance> activityInstances,
                                                        List<HistoricTaskInstance> historicTasks) {
        Map<String, HistoricActivityInstance> nodeIdAndActMap = activityInstances.stream()
                .collect(Collectors.toMap(a -> nodeId(a.getActivityId(), a.getExecutionId()), a -> a, (a, b) -> a));
        Map<String, HistoricTaskInstance> nodeIdAndTaskMap = historicTasks.stream()
                .collect(Collectors.toMap(a -> nodeId(a.getTaskDefinitionKey(), a.getExecutionId()), a -> a, (a, b) -> a));


        Map<String, EngineApprovalFlowNode2> nodeMap = makeUpAllFlowNode(process, variableInstances, nodeIdAndActMap, nodeIdAndTaskMap);
        List<HistoricActivityInstance> startActivities = findStartEventActivities(nodeIdAndActMap);
        HistoricActivityInstance rootFlowStartAct = findRootFlowStartActivity(process, startActivities);
        Map<String, List<String>> outGoingsMap = makeUpOutGoings(nodeMap, process, activityInstances);
        List<HistoricActivityInstance> subProcessActivities = findSubProcessNodes(nodeMap, nodeIdAndActMap);
        Map<String, String> flowStartNodeMap = makeUpFlowStartNodes(startActivities);
        Map<String, List<String>> subFlowsMap = makeUpSubFlows(subProcessActivities, nodeMap);

        return EngineApprovalFlowResult.builder()
                .rootFlowId(flowId(rootFlowStartAct.getExecutionId()))
                .flowStartNodeMap(flowStartNodeMap)
                .outGoingsMap(outGoingsMap)
                .subFlowsMap(subFlowsMap)
                .nodeMap(nodeMap)
                .build();
    }

    private HistoricActivityInstance findRootFlowStartActivity(Process process, List<HistoricActivityInstance> startActivities) {
        FlowElement rootStartEvent = process.getInitialFlowElement();
        return startActivities.stream()
                .filter(startAct -> Objects.equals(startAct.getActivityId(), rootStartEvent.getId()))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("cannot find root start activity"));
    }

    /**
     * 节点-子流程映射。
     *
     * @param subProcessActivities 子流程节点活动实例
     * @param nodeMap
     * @return key: nodeId, value: subFlowIds,节点的子流程实例的flowId列表
     */
    private Map<String, List<String>> makeUpSubFlows(List<HistoricActivityInstance> subProcessActivities, Map<String, EngineApprovalFlowNode2> nodeMap) {
        Map<String, List<String>> subFlowsMap = new HashMap<>();
        for (HistoricActivityInstance act : subProcessActivities) {
            subFlowsMap.computeIfAbsent(nodeId(act.getActivityId(), act.getExecutionId()), k -> new ArrayList<>()).add(flowId(act.getExecutionId()));
        }
        return subFlowsMap;
    }

    private List<HistoricActivityInstance> findSubProcessNodes(Map<String, EngineApprovalFlowNode2> nodeMap,
                                                               Map<String, HistoricActivityInstance> nodeIdAndActMap) {

        List<String> subProcessNodeIds = new ArrayList<>();
        nodeMap.forEach((nodeId, node) -> {
            if (SUB_PROCESS_TYPE_NAME.equals(node.getNodeType())) {
                subProcessNodeIds.add(nodeId);
            }
        });
        return subProcessNodeIds.stream().map(nodeIdAndActMap::get).collect(Collectors.toList());
    }

    private Map<String, List<String>> makeUpOutGoings(Map<String, EngineApprovalFlowNode2> nodeMap,
                                                      Process process,
                                                      List<HistoricActivityInstance> activityInstances) {
        Map<String, List<HistoricActivityInstance>> actIdAndInstancesMap = activityInstances.stream().collect(Collectors.groupingBy(HistoricActivityInstance::getActivityId));
        Map<String, List<String>> outGoingsMap = new HashMap<>();
        nodeMap.forEach((nodeId, node) -> {
            FlowNode flowElement = (FlowNode) process.getFlowElement(node.getActId(), true);
            List<String> nextActIds = flowElement.getOutgoingFlows().stream().map(SequenceFlow::getTargetRef).collect(Collectors.toList());
            if (nextActIds.size() != 0) {
                for (String nextActId : nextActIds) {
                    List<HistoricActivityInstance> nextActs = actIdAndInstancesMap.get(nextActId);

                }
                outGoingsMap.put(nodeId, nextActIds);
            }
        });
        return outGoingsMap;
    }

    /**
     * 组建流程集合，包括子流程。
     *
     * @param startActivities 流程（包括子流程）中所有的startEvent活动实例
     * @return key: flowId, value: 对应审批流的起始节点的nodeId
     */
    private Map<String, String> makeUpFlowStartNodes(List<HistoricActivityInstance> startActivities) {
        Map<String, String> flowStartNodeMap = new HashMap<>();
        for (HistoricActivityInstance startAct : startActivities) {
            flowStartNodeMap.put(flowId(startAct.getExecutionId()), nodeId(startAct.getActivityId(), startAct.getExecutionId()));
        }
        return flowStartNodeMap;
    }

    private List<HistoricActivityInstance> findStartEventActivities(Map<String, HistoricActivityInstance> nodeIdAndActMap) {
        List<HistoricActivityInstance> startActivities = new ArrayList<>();
        nodeIdAndActMap.forEach((nodeId, activity) -> {
            if ("startEvent".equals(activity.getActivityType())) {
                startActivities.add(activity);
            }
        });
        return startActivities;
    }

    private Map<String, EngineApprovalFlowNode2> makeUpAllFlowNode(Process process,
                                                                   List<HistoricVariableInstance> variableInstances,
                                                                   Map<String, HistoricActivityInstance> nodeIdAndActMap,
                                                                   Map<String, HistoricTaskInstance> nodeIdAndTaskMap) {
        Map<String, EngineApprovalFlowNode2> nodeMap = new HashMap<>();
        nodeIdAndActMap.forEach((nodeId, act) -> {
            // 去掉网关
            if (act.getActivityType().toLowerCase().endsWith("gateway")) {
                return;
            }
            // 子流程实例
            String elementVariable = null;
            String collectionVariable = null;
            if (SUB_PROCESS_TYPE_NAME.equals(act.getActivityType())) {
                SubProcess subProcess = (SubProcess) process.getFlowElement(act.getActivityId(), true);
                MultiInstanceLoopCharacteristics loopCharacteristics = subProcess.getLoopCharacteristics();
                elementVariable = loopCharacteristics.getElementVariable();
                collectionVariable = loopCharacteristics.getInputDataItem();
            }
            EngineApprovalFlowNode2 node = EngineApprovalFlowNode2.builder()
                    .nodeId(nodeId)
                    .actId(act.getActivityId())
                    .nodeName(act.getActivityName())
                    .nodeType(act.getActivityType())
                    .elementVariable(elementVariable)
                    .collectionVariable(collectionVariable)
                    .taskInfo(nodeIdAndTaskMap.get(nodeId))
                    .build();
            nodeMap.put(node.getNodeId(), node);
        });
        return nodeMap;
    }

    private ApprovalFlow makeUpRootApprovalFlow(Process process,
                                                Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                                Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        FlowNode startNode = (FlowNode) process.getInitialFlowElement();
        HistoricActivityInstance startAct = actIdAndActivitiesMap.get(startNode.getId()).get(0);
        ApprovalFlowNode root = ApprovalFlowNode.builder()
                .nodeId(startAct.getActivityId())
                .nodeName(startAct.getActivityName())
                .build();
        Map<SubProcess, ApprovalFlowNode> subProcessCollector = new HashMap<>();
        makeUpNextNodes(root, actIdAndActivitiesMap, process.getFlowElementMap(), actIdAndHisTasksMap, subProcessCollector);
        makeUpSubPrcessApprovalFlow(subProcessCollector, actIdAndActivitiesMap, actIdAndHisTasksMap);
        return ApprovalFlow.builder().approvalFlowNode(root).flowType(ApprovalFlow.ApprovalFlowTypeEnum.ROOT).build();
    }

    private List<ApprovalFlow> makeUpSubApprovalFlow(ApprovalFlowNode subProcessNode,
                                                     SubProcess subProcess,
                                                     Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                                     Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        List<ApprovalFlow> subFlows = new ArrayList<>();

        FlowNode startNode = (FlowNode) subProcess.getFlowElements().stream()
                .filter(flowElement -> flowElement instanceof StartEvent)
                .findFirst().orElseThrow(() -> new RuntimeException("subProcess start event not found"));
        List<HistoricActivityInstance> startActs = actIdAndActivitiesMap.get(startNode.getId());

        startActs.forEach(startAct -> {
            ApprovalFlowNode root = ApprovalFlowNode.builder()
                    .nodeId(startAct.getActivityId())
                    .nodeName(startAct.getActivityName() != null ? startAct.getActivityName() : startAct.getActivityType())
                    .collectionVariable(subProcessNode.getCollectionVariable())
                    .elementVariable(subProcessNode.getElementVariable())
                    .elementVariableValues(subProcessNode.getElementVariableValues())
                    .executionId(startAct.getExecutionId())
                    .build();
            Map<SubProcess, ApprovalFlowNode> subProcessCollector = new HashMap<>();
            makeUpNextNodes(root, actIdAndActivitiesMap, subProcess.getFlowElementMap(), actIdAndHisTasksMap, subProcessCollector);
            makeUpSubPrcessApprovalFlow(subProcessCollector, actIdAndActivitiesMap, actIdAndHisTasksMap);
            subFlows.add(ApprovalFlow.builder().approvalFlowNode(root).flowType(ApprovalFlow.ApprovalFlowTypeEnum.SUB).build());
        });

        return subFlows;
    }

    private void makeUpSubPrcessApprovalFlow(Map<SubProcess, ApprovalFlowNode> subProcessCollector,
                                             Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                             Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        subProcessCollector.forEach((subProcess, subProcessNode) -> {
            List<ApprovalFlow> approvalFlows = makeUpSubApprovalFlow(subProcessNode, subProcess, actIdAndActivitiesMap, actIdAndHisTasksMap);
            subProcessNode.setSubFlows(approvalFlows);
        });
    }

    private void makeUpNextNodes(ApprovalFlowNode curNode,
                                 Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                 Map<String, FlowElement> flowElementMap,
                                 Map<String, List<TaskInfo>> actIdAndHisTasksMap,
                                 Map<SubProcess, ApprovalFlowNode> subProcessCollector) {
        FlowNode flowNode = (FlowNode) flowElementMap.get(curNode.getNodeId());
        List<FlowNode> nextNodes = getNextActiveNodes(flowNode, actIdAndActivitiesMap);
        if (nextNodes == null || nextNodes.size() == 0) {
            return;
        }

        for (FlowNode nextNode : nextNodes) {
            if (nextNode instanceof SubProcess) {
                SubProcess subProcess = (SubProcess) nextNode;
                Set<String> executionIds = getSubProcessExecutionIds(actIdAndActivitiesMap, subProcess);
                MultiInstanceLoopCharacteristics loopCharacteristics = subProcess.getLoopCharacteristics();
                List<Object> values = getElementVariableValues("7501", loopCharacteristics, executionIds);
                ApprovalFlowNode node = ApprovalFlowNode.builder()
                        .nodeId(nextNode.getId())
                        .nodeName(nextNode.getName())
                        .collectionVariable(loopCharacteristics.getInputDataItem())
                        .elementVariable(loopCharacteristics.getElementVariable())
                        .elementVariableValues(values)
                        .build();
                subProcessCollector.put((SubProcess) nextNode, node);
                curNode.addNextNode(node);
            } else {
                String executionId = null;
                List<HistoricActivityInstance> acts = actIdAndActivitiesMap.get(nextNode.getId());
                if (acts != null && acts.size() != 0) {
                    executionId = acts.get(0).getExecutionId();
                }
                ApprovalFlowNode node = ApprovalFlowNode.builder()
                        .nodeId(nextNode.getId())
                        .nodeName(nextNode.getName())
                        .executionId(executionId)
                        .build();
                findAndCompleteTask(node, actIdAndHisTasksMap);
                curNode.addNextNode(node);
            }
        }
        curNode.getNextNodes().forEach(cn -> makeUpNextNodes(cn, actIdAndActivitiesMap, flowElementMap, actIdAndHisTasksMap, subProcessCollector));
    }

    private Set<String> getSubProcessExecutionIds(Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap, SubProcess subProcess) {
        Set<String> executionIds = new HashSet<>();
        List<HistoricActivityInstance> acts = actIdAndActivitiesMap.get(subProcess.getId());
        if (acts != null && acts.size() != 0) {
            executionIds = acts.stream().map(HistoricActivityInstance::getExecutionId).collect(Collectors.toSet());
        }
        return executionIds;
    }

    private List<Object> getElementVariableValues(String procInstId, MultiInstanceLoopCharacteristics loopCharacteristics, Set<String> executionIds) {
        List<HistoricVariableInstance> elementValues = normalEngine.getHistoryService().createHistoricVariableInstanceQuery()
                .processInstanceId(procInstId)
                .executionIds(executionIds)
                .variableName(loopCharacteristics.getElementVariable())
                .list();
        return elementValues.stream().map(HistoricVariableInstance::getValue).collect(Collectors.toList());
    }

    private void findAndCompleteTask(ApprovalFlowNode node, Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        List<TaskInfo> taskInfos = actIdAndHisTasksMap.get(node.getNodeId());
        if (taskInfos == null || taskInfos.size() == 0) {
            return;
        }
        if (node.getExecutionId() != null) {
            taskInfos = taskInfos.stream().filter(task -> task.getExecutionId().equals(node.getExecutionId())).collect(Collectors.toList());
        }
        node.setTaskEntities(taskInfos);
    }

    private List<FlowNode> getNextActiveNodes(FlowNode flowNode, Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap) {
        List<FlowNode> activeNodes = new ArrayList<>();
        for (SequenceFlow out : flowNode.getOutgoingFlows()) {
            FlowNode targetFlowElement = (FlowNode) out.getTargetFlowElement();
            if (actIdAndActivitiesMap.containsKey(targetFlowElement.getId())) {
                activeNodes.add(targetFlowElement);
            }
        }

        List<FlowNode> result = new ArrayList<>();
        for (FlowNode node : activeNodes) {
            if (node instanceof Gateway) {
                List<FlowNode> nodesComeFromGateway = getNextActiveNodes(node, actIdAndActivitiesMap);
                result.addAll(nodesComeFromGateway);
            } else {
                result.add(node);
            }
        }
        return result;
    }

}
